package redis

import (
	"context"
	"errors"
	"fmt"
	"github.com/redis/go-redis/v9"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"golang-sgg-daijia/pkg/log"
	"time"
)

type RedisClient interface {
	Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
	Get(ctx context.Context, key string) (string, error)
	GeoDel(ctx context.Context, key string, member ...string) (bool, error)
	GeoAdd(ctx context.Context, key string, geoLocation ...*redis.GeoLocation) (bool, error)
	GeoRadiusByMember(ctx context.Context, key, member string,
		query *redis.GeoRadiusQuery) ([]redis.GeoLocation, error)
	GeoRadius(ctx context.Context, key string, longitude float64, latitude float64,
		query *redis.GeoRadiusQuery) ([]redis.GeoLocation, error)
	SIsMember(ctx context.Context, key string, member interface{}) (bool, error)
	SAdd(ctx context.Context, key string, member ...interface{}) (int64, error)
	Expire(ctx context.Context, key string, expiration time.Duration) (bool, error)
	LPush(ctx context.Context, key string, value ...interface{}) (int64, error)
	LLen(ctx context.Context, key string) (int64, error)
	LPop(ctx context.Context, key string) (string, error)
	Del(ctx context.Context, key string) (int64, error)
	Exists(ctx context.Context, key string) (bool, error)
}

type redisClient struct {
	rdb    *redis.Client
	logger *log.Logger
}

func NewRedisClient(
	rdb *redis.Client,
	logger *log.Logger,
) RedisClient {
	return &redisClient{
		rdb:    rdb,
		logger: logger,
	}
}

func NewRedis(conf *viper.Viper) *redis.Client {
	rdb := redis.NewClient(&redis.Options{
		Addr:     conf.GetString("data.redis.addr"),
		Password: conf.GetString("data.redis.password"),
		DB:       conf.GetInt("data.redis.db"),
	})

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_, err := rdb.Ping(ctx).Result()
	if err != nil {
		panic(fmt.Sprintf("redis error: %s", err.Error()))
	}

	return rdb
}

func (r *redisClient) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
	err := r.rdb.Set(ctx, key, value, expiration).Err()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|Set(", zap.Any("data", map[string]interface{}{
			"key":        key,
			"value":      value,
			"expiration": expiration,
		}), zap.Error(err))
		return err
	}
	return nil
}

func (r *redisClient) Get(ctx context.Context, key string) (string, error) {
	value, err := r.rdb.Get(ctx, key).Result()
	if err != nil {
		if errors.Is(err, redis.Nil) {
			return "", nil
		}
		r.logger.WithContext(ctx).Error("rediscli|Get(", zap.Any("data", map[string]interface{}{
			"key": key,
		}), zap.Error(err))
		return "", err
	}
	return value, nil
}

func (r *redisClient) GeoDel(ctx context.Context, key string, member ...string) (bool, error) {
	err := r.rdb.ZRem(ctx, key, member).Err()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|Del(", zap.Any("data", map[string]interface{}{
			"key": key,
		}), zap.Error(err))
		return false, err
	}
	return true, nil
}

func (r *redisClient) GeoAdd(ctx context.Context, key string, geoLocation ...*redis.GeoLocation) (bool, error) {
	err := r.rdb.GeoAdd(ctx, key, geoLocation...).Err()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|GeoAdd(", zap.Any("data", map[string]interface{}{
			"key": key,
		}), zap.Error(err))
		return false, err
	}
	return true, nil
}

func (r *redisClient) GeoRadiusByMember(ctx context.Context, key, member string,
	query *redis.GeoRadiusQuery) ([]redis.GeoLocation, error) {
	geoLoactions, err := r.rdb.GeoRadiusByMember(ctx, key, member, query).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|GeoRadiusByMember(", zap.Any("data", map[string]interface{}{
			"key":      key,
			"member":   member,
			"query":    query,
		}), zap.Error(err))
		return nil, err
	}
	return geoLoactions, nil
}

func (r *redisClient) GeoRadius(ctx context.Context, key string, longitude float64, latitude float64,
	query *redis.GeoRadiusQuery) ([]redis.GeoLocation, error) {
	geoLoactions, err := r.rdb.GeoRadius(ctx, key, longitude, latitude, query).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|GeoRadius(", zap.Any("data", map[string]interface{}{
			"key":      key,
		}), zap.Error(err))
		return nil, err
	}
	return geoLoactions, nil
}

// SIsMember 判断判断 member 元素是否是集合 key 的成员
func (r *redisClient) SIsMember(ctx context.Context, key string, member interface{}) (bool, error) {
	isMember, err := r.rdb.SIsMember(ctx, key, member).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|IsMember(", zap.Any("data", map[string]interface{}{
			"key":      key,
			"member":   member,
		}), zap.Error(err))
		return false, err
	}
	return isMember, nil
}

func (r *redisClient) SAdd(ctx context.Context, key string, member ...interface{}) (int64, error) {
	n, err := r.rdb.SAdd(ctx, key, member...).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|SAdd(", zap.Any("data", map[string]interface{}{
			"key":      key,
			"member":   member,
		}), zap.Error(err))
		return 0, err
	}
	return n,nil
}

func (r *redisClient) Expire(ctx context.Context, key string, expiration time.Duration) (bool, error) {
	flag, err := r.rdb.Expire(ctx, key, expiration).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|Expire(", zap.Any("data", map[string]interface{}{
			"key":        key,
			"expiration": expiration,
		}), zap.Error(err))
		return false, err
	}
	return flag, nil
}

func (r *redisClient) LPush(ctx context.Context, key string, value ...interface{}) (int64, error) {
	n, err := r.rdb.LPush(ctx, key, value...).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|LPush(", zap.Any("data", map[string]interface{}{
			"key":      key,
			"value":    value,
		}), zap.Error(err))
		return 0, err
	}
	return n, nil
}

func (r *redisClient) LLen(ctx context.Context, key string) (int64, error) {
	n, err := r.rdb.LLen(ctx, key).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|LLen(", zap.Any("data", map[string]interface{}{
			"key":      key,
		}), zap.Error(err))
		return 0, err
	}
	return n, nil
}

func (r *redisClient) LPop(ctx context.Context, key string) (string, error) {
	value, err := r.rdb.LPop(ctx, key).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|LPop(", zap.Any("data", map[string]interface{}{
			"key":      key,
		}), zap.Error(err))
		return "", err
	}
	return value, nil
}

func (r *redisClient) Del(ctx context.Context, key string) (int64, error) {
	n, err := r.rdb.Del(ctx, key).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|Del(", zap.Any("data", map[string]interface{}{
			"key":      key,
		}), zap.Error(err))
		return 0, err
	}
	return n, err
}

func (r *redisClient) Exists(ctx context.Context, key string) (bool, error) {
	n, err := r.rdb.Exists(ctx, key).Result()
	if err != nil {
		r.logger.WithContext(ctx).Error("rediscli|Exists(", zap.Any("data", map[string]interface{}{
			"key":      key,
		}), zap.Error(err))
		return false, err
	}
	return n != 0, nil
}